package com.huntmobi.springsecuritydemo.base.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huntmobi.springsecuritydemo.base.vo.SysLoginSuccess;
import com.huntmobi.springsecuritydemo.mapper.SysMenuMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <p>
 *
 * @Author：zouzhimin
 * @description：动态权限控制，基于数据库
 * @date：crealed in 11:26 2020/07/08
 * </P>
 **/
@Component("rbacAuthority")
public class RbacAuthority {

    @Autowired
    private SysMenuMapper sysMenuMapper;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private ServerProperties serverProperties;

    public boolean hasPermission(HttpServletRequest request, Authentication authentication) throws JsonProcessingException {
        // 1.定义临时权限值
        AtomicBoolean hasPermission = new AtomicBoolean(false);
        // 2.获取登录信息进行实体转换
        SysLoginSuccess sysLoginSuccess = this.sysLoginSuccess(authentication);
        // 3.根据实体，校验权限
        isRoleAuthority(sysLoginSuccess, hasPermission, request);
        return hasPermission.get();
    }

    /**
     * 2.实体转换
     */
    private SysLoginSuccess sysLoginSuccess(Authentication authentication) throws JsonProcessingException{
        //强转登录成功后，获取用户的登录信息，json数据
        String jsonStr = (String) authentication.getPrincipal();
        return new ObjectMapper().readValue(jsonStr, new TypeReference<SysLoginSuccess>() {});
    }

    /**
     * 3.根据实体，校验权限
     */
    private void isRoleAuthority(SysLoginSuccess sysLoginSuccess, AtomicBoolean hasPermission, HttpServletRequest request) {
        // 没有账户，直接返回无权限访问
        if (!ObjectUtils.isEmpty(sysLoginSuccess)) {
            //多角色查询权限
            for (HashMap sysRole : sysLoginSuccess.getRoles()) {
                //判断redis中存不存在该用户下面的权限路径,存在，直接返回 true
                if (!ObjectUtils.isEmpty(redisTemplate.opsForHash().get(sysRole.get("roleName").toString(), request.getRequestURI().replaceAll(serverProperties.getServlet().getContextPath(), "")))) {
                    hasPermission.set(true);
                } else {
                    HashMap<String, Object> hashMap = new HashMap<>();
                    //重新权限获取
                    userAuthority(hashMap, sysRole.get("roleName").toString());
                    //判断是否有权限
                    isAuthority(hasPermission, hashMap, request);
                }
            }
        }
    }

    /**
     * 判断是否有权限
     */
    private void isAuthority(AtomicBoolean hasPermission, HashMap<String, Object> hashMap, HttpServletRequest request) {
        // 开始判断请求连接有没有权限
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        Iterator iterator = hashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            if (antPathMatcher.match(entry.getKey().toString(), request.getRequestURI().replaceAll(serverProperties.getServlet().getContextPath(), ""))) {
                hasPermission.set(true);
                break;
            }
        }
    }

    /**
     * 获取权限，并把权限存放在redis中
     */
    private void userAuthority(HashMap<String, Object> hashMap, String roleName) {
        sysMenuMapper.findMenuByUserName(roleName).forEach(sysMenu -> {
            hashMap.put(sysMenu.getPath(), sysMenu.getName());
        });

        // 把该用户下面的路径全部放入redis中，减少请求数据库
        redisTemplate.opsForHash().putAll(roleName, hashMap);
    }


    /**
     * 重置权限，只要清空我们redis中原有的值就可以了
     */
    public void resetAuthority(List<String> roles) {
        if (!roles.isEmpty()) {
            //利用redis中的key不可重复，重复就覆盖特性重置权限
            roles.forEach(role -> {
                userAuthority(new HashMap(), role);
            });
        }
    }
}
